#include "common.h"
#include "memory.h"
#include <string.h>
#include <elf.h>

#define ELF_OFFSET_IN_DISK 0
#define USERPROC_START ((void*)0x800000)

#ifdef HAS_DEVICE
void ide_read(uint8_t *, uint32_t, uint32_t);
#else
void ramdisk_read(uint8_t *, uint32_t, uint32_t);
#endif

#define STACK_SIZE (1 << 20)

void create_video_mapping();
uint32_t get_ucr3();

uint32_t loader() {
	Elf32_Ehdr *elf;
	Elf32_Phdr *ph = NULL;

	uint8_t buf[4096];

#ifdef HAS_DEVICE
	ide_read(buf, ELF_OFFSET_IN_DISK, 4096);
#else
	ramdisk_read(buf, ELF_OFFSET_IN_DISK, 4096);
#endif

	elf = (void*)buf;

	/* fix the magic number with the correct one */
	const uint32_t elf_magic = 0x464c457f;
	uint32_t *p_magic = (void *)buf;
	nemu_assert(*p_magic == elf_magic);
    
	/* Load each program segment */
    ph = (void*)elf + (elf->e_phoff);

    //nemu_assert(ph==(void *)0x7ffefdc);
    nemu_assert(elf->e_phoff==52);
	nemu_assert((void*)ph->p_paddr==(void*)0x800000);
	nemu_assert(ph->p_offset==0);
	nemu_assert(ph->p_type==PT_LOAD);
	nemu_assert(ph->p_filesz==0xd4);
	nemu_assert(ph->p_memsz==0xd4);
	nemu_assert(elf->e_phnum==5);

	//ph = ph + 1;
    //nemu_assert(ph==(void *)0x7fff00e);
	//nemu_assert((void*)ph->p_paddr==(void*)0x801000);
	//nemu_assert(ph->p_offset==0x1000);
	//nemu_assert(ph->p_type==PT_LOAD);
	//nemu_assert(ph->p_filesz==0xde);

	//memcpy((void*)(ph->p_paddr), (void*)elf+(ph->p_offset), ph->p_filesz);

	for(int i = 0; i < elf->e_phnum; i++) {
		/* Scan the program header table, load each segment into memory */
		if(ph->p_type == PT_LOAD) {
            
			/* Done: read the content of the segment from the ELF file 
			 * to the memory region [VirtAddr, VirtAddr + FileSiz)
			 */
			memcpy((void*)(ph->p_paddr), (void*)(ph->p_offset), ph->p_filesz);
            
			/* Done: zero the memory region 
			 * [VirtAddr + FileSiz, VirtAddr + MemSiz)
			 */
			//for(int j=0; j < (ph->p_memsz - ph->p_filesz); j++){
            //    *(uint8_t *)(ph->p_paddr + ph->p_filesz + j) = 0xff;
			//}

#ifdef IA32_PAGE
			/* Record the program break for future use. */
			extern uint32_t brk;
			uint32_t new_brk = ph[i].p_vaddr + ph[i].p_memsz - 1;
			if(brk < new_brk) { brk = new_brk; }
#endif
		}
		ph = ph + 1;
		//nemu_assert(ph==(void*)0x7ffeffc);
	}
    //memcpy((void*)(0x801000), (void*)(0x1000), 0xfd);

	volatile uint32_t entry = elf->e_entry;
	//nemu_assert(entry == 0x801020);

#ifdef IA32_PAGE
	mm_malloc(KOFFSET - STACK_SIZE, STACK_SIZE);

#ifdef HAS_DEVICE
	create_video_mapping();
#endif

	write_cr3(get_ucr3());
#endif

	return entry;
}
